#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Apr  4 09:39:36 2025

@author: zhengyijun
"""

"""
透析患者质控指标统计分析系统 - 完整修复版
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter import font as tkfont
import pandas as pd
import numpy as np

# 设置matplotlib后端为非GUI模式，避免线程问题
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

from datetime import datetime, timedelta
import os
import re
import sys
import threading
import time
from pandas.api.types import is_numeric_dtype
from PIL import Image, ImageTk
from pptx import Presentation
from pptx.util import Inches
from docx import Document
from docx.shared import Inches as DocxInches

# ==================== 分析功能部分 ====================
# 全局错误记录
global_errors = []

def validate_data(df, sheet_name):
    """验证数据格式并自动转换"""
    errors = []
    warnings = []
    
    # 数值型字段检查
    numeric_cols = ['血红蛋白', '血钾', '白蛋白', '年龄', '平均体重增长率', 
                   'Kt/V', 'URR', '血磷', '血钙', '甲状旁腺激素', '铁蛋白']
    
    for col in numeric_cols:
        if col in df.columns:
            if not is_numeric_dtype(df[col]):
                try:
                    df[col] = pd.to_numeric(df[col], errors='raise')
                    warnings.append(f"已转换 {col} 为数值类型")
                except:
                    invalid_values = df[pd.to_numeric(df[col], errors='coerce').isna()][col].unique()
                    error_msg = f"工作表【{sheet_name}】- {col} 包含非数值数据: {', '.join(map(str, invalid_values[:10]))}" + \
                              ("..." if len(invalid_values) > 10 else "")
                    errors.append(error_msg)
                    global_errors.extend([[sheet_name, col, f"非数值数据: {str(val)}"] for val in invalid_values[:50]])
                    df[col] = pd.to_numeric(df[col], errors='coerce')

    # 日期字段检查 - 严格验证YYYY-MM-DD格式
    date_cols = ['血常规检查日期', '铁蛋白检查日期', '转铁蛋白饱和度检验日期',
                '透析充分性检查日期', '甲状旁腺激素检查日期', '前白蛋白检查日期',
                'C反应蛋白检查日期', 'β2微球蛋白检查日期', '启用日期']
    
    date_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}$')  # YYYY-MM-DD格式正则
    
    for col in date_cols:
        if col in df.columns:
            invalid_dates = []
            for idx, val in enumerate(df[col]):
                if pd.isna(val):
                    continue
                if not (isinstance(val, str) and date_pattern.match(str(val))):
                    if not (isinstance(val, pd.Timestamp) and val.strftime('%Y-%m-%d') == str(val)[:10]):
                        invalid_dates.append(f"行{idx+2}:{val}")
                        global_errors.append([sheet_name, col, f"无效日期格式: 行{idx+2}:{val}"])
                        if len(invalid_dates) >= 50:
                            invalid_dates.append("...(更多错误未显示)")
                            break
            
            if invalid_dates:
                error_msg = f"工作表【{sheet_name}】- {col} 日期格式应为YYYY-MM-DD，发现错误: {'; '.join(invalid_dates[:5])}" + \
                          ("..." if len(invalid_dates) > 5 else "")
                errors.append(error_msg)
            
            df[col] = pd.to_datetime(df[col], format='%Y-%m-%d', errors='coerce')

    # 分类字段检查
    if '性别' in df.columns:
        invalid_sex = df[~df['性别'].isin(['男', '女'])]['性别'].dropna().unique()
        if len(invalid_sex) > 0:
            error_msg = f"工作表【{sheet_name}】- 性别包含无效值: {', '.join(invalid_sex)}"
            errors.append(error_msg)
            global_errors.extend([[sheet_name, '性别', f"无效值: {val}"] for val in invalid_sex])

    return df, errors, warnings

def initialize_results():
    """初始化结果字典"""
    return {
        '月份': [],
        '总人数': [],
        '男性比例': [], '男性比例_分子': [], '男性比例_分母': [],
        '女性比例': [], '女性比例_分子': [], '女性比例_分母': [],
        '血红蛋白<110比例': [], '血红蛋白<110比例_分子': [], '血红蛋白<110比例_分母': [],
        '血红蛋白110-130比例': [], '血红蛋白110-130比例_分子': [], '血红蛋白110-130比例_分母': [],
        '血红蛋白>130比例': [], '血红蛋白>130比例_分子': [], '血红蛋白>130比例_分母': [],
        '血钾<3.5比例': [], '血钾<3.5比例_分子': [], '血钾<3.5比例_分母': [],
        '血钾3.5-5比例': [], '血钾3.5-5比例_分子': [], '血钾3.5-5比例_分母': [],
        '血钾>5比例': [], '血钾>5比例_分子': [], '血钾>5比例_分母': [],
        '白蛋白>35比例': [], '白蛋白>35比例_分子': [], '白蛋白>35比例_分母': [],
        '高血压控制率(<65岁)': [], '高血压控制率(<65岁)_分子': [], '高血压控制率(<65岁)_分母': [],
        '高血压控制率(≥65岁)': [], '高血压控制率(≥65岁)_分子': [], '高血压控制率(≥65岁)_分母': [],
        '高血压总控制率': [], '高血压总控制率_分子': [], '高血压总控制率_分母': [],
        '体重增长率<5%比例': [], '体重增长率<5%比例_分子': [], '体重增长率<5%比例_分母': [],
        '血常规定时检验合格率': [], '血常规定时检验合格率_分子': [], '血常规定时检验合格率_分母': [],
        '铁蛋白及转铁蛋白时效合格率': [], '铁蛋白及转铁蛋白时效合格率_分子': [], '铁蛋白及转铁蛋白时效合格率_分母': [],
        'KtV>1.2且URR>65%比例': [], 'KtV>1.2且URR>65%比例_分子': [], 'KtV>1.2且URR>65%比例_分母': [],
        '透析充分性定时检验合格率': [], '透析充分性定时检验合格率_分子': [], '透析充分性定时检验合格率_分母': [],
        'CKD-MBD达标率': [], 'CKD-MBD达标率_分子': [], 'CKD-MBD达标率_分母': [],
        'CKD-MBD未达标率': [], 'CKD-MBD未达标率_分子': [], 'CKD-MBD未达标率_分母': [],
        '血磷<1.13比例': [], '血磷<1.13比例_分子': [], '血磷<1.13比例_分母': [],
        '血磷1.13-1.78比例': [], '血磷1.13-1.78比例_分子': [], '血磷1.13-1.78比例_分母': [],
        '血磷>1.78比例': [], '血磷>1.78比例_分子': [], '血磷>1.78比例_分母': [],
        '血钙<2.1比例': [], '血钙<2.1比例_分子': [], '血钙<2.1比例_分母': [],
        '血钙2.1-2.5比例': [], '血钙2.1-2.5比例_分子': [], '血钙2.1-2.5比例_分母': [],
        '血钙>2.5比例': [], '血钙>2.5比例_分子': [], '血钙>2.5比例_分母': [],
        'PTH<150比例': [], 'PTH<150比例_分子': [], 'PTH<150比例_分母': [],
        'PTH150-300比例': [], 'PTH150-300比例_分子': [], 'PTH150-300比例_分母': [],
        'PTH>300比例': [], 'PTH>300比例_分子': [], 'PTH>300比例_分母': [],
        'iPTH定时检验合格率': [], 'iPTH定时检验合格率_分子': [], 'iPTH定时检验合格率_分母': [],
        '前白蛋白定时检验合格率': [], '前白蛋白定时检验合格率_分子': [], '前白蛋白定时检验合格率_分母': [],
        'CRP定时检验合格率': [], 'CRP定时检验合格率_分子': [], 'CRP定时检验合格率_分母': [],
        'β2微球蛋白定时检验合格率': [], 'β2微球蛋白定时检验合格率_分子': [], 'β2微球蛋白定时检验合格率_分母': [],
        '血液生化定时检验完成率': [], '血液生化定时检验完成率_分子': [], '血液生化定时检验完成率_分母': [],
        '感染筛查时效合格率': [], '感染筛查时效合格率_分子': [], '感染筛查时效合格率_分母': [],
        '铁蛋白平均值': [], '铁蛋白最大值': [], '铁蛋白最小值': [], '铁蛋白标准差': [],
        '动静脉内瘘长期生存率': [], '动静脉内瘘长期生存率_分子': [], '动静脉内瘘长期生存率_分母': []
    }

def process_data(sheets):
    """主数据处理函数"""
    results = initialize_results()
    
    for sheet_name, df in sheets.items():
        if df.empty:
            continue
            
        if not (sheet_name.isdigit() and len(sheet_name) == 6):
            continue
            
        try:
            year, month = int(sheet_name[:4]), int(sheet_name[4:6])
            sheet_date = datetime(year, month, 1)
            last_day = (sheet_date.replace(month=month % 12 + 1, day=1) - timedelta(days=1)).day
            sheet_end_date = datetime(year, month, last_day)
        except:
            continue

        # 数据验证
        df, errors, warnings = validate_data(df.copy(), sheet_name)
        if errors:
            print(f"【{sheet_name}】数据问题: {errors[0]}{'...' if len(errors)>1 else ''}")

        # 指标计算
        total_patients = len(df)
        results['总人数'].append(total_patients)
        
        # 性别统计
        male_count = (df['性别'] == '男').sum()
        female_count = (df['性别'] == '女').sum()
        
        # 血红蛋白分层
        hemoglobin = df['血红蛋白']
        hb_lt_110 = (hemoglobin < 110).sum()
        hb_110_130 = ((hemoglobin >= 110) & (hemoglobin <= 130)).sum()
        hb_gt_130 = (hemoglobin > 130).sum()
        
        # 血钾分层统计
        potassium = df['血钾']
        k_lt_35 = (potassium < 3.5).sum()
        k_35_5 = ((potassium >= 3.5) & (potassium <= 5)).sum()
        k_gt_5 = (potassium > 5).sum()
        
        # 白蛋白统计
        albumin_gt_35 = (df['白蛋白'] > 35).sum()
        
        # 高血压控制率
        age = df['年龄']
        sbp = df['平均透前收缩压']
        dbp = df['平均透前舒张压']
        
        young_controlled = ((age < 65) & (sbp < 140) & (dbp < 90)).sum()
        elder_controlled = ((age >= 65) & (sbp < 160) & (dbp < 90)).sum()
        total_controlled = young_controlled + elder_controlled
        young_total = (age < 65).sum()
        elder_total = (age >= 65).sum()
        
        # 体重增长率
        weight_growth_lt_5 = (df['平均体重增长率'] < 5).sum()
        
        # 定时检验性统计
        def check_timeliness(col_name, days_threshold):
            if col_name in df.columns:
                test_date = pd.to_datetime(df[col_name], errors='coerce')
                valid = (sheet_end_date - test_date).dt.days <= days_threshold
                return valid.sum(), len(valid.dropna())
            return 0, 0
        
        blood_test_timely_num, blood_test_timely_den = check_timeliness('血常规检查日期', 90)
        ferritin_test_timely_num, ferritin_test_timely_den = check_timeliness('铁蛋白检查日期', 180)
        transferrin_test_timely_num, transferrin_test_timely_den = check_timeliness('转铁蛋白饱和度检验日期', 180)
        dialysis_test_timely_num, dialysis_test_timely_den = check_timeliness('透析充分性检查日期', 180)
        pth_test_timely_num, pth_test_timely_den = check_timeliness('甲状旁腺激素检查日期', 180)
        prealbumin_test_timely_num, prealbumin_test_timely_den = check_timeliness('前白蛋白检查日期', 180)
        crp_test_timely_num, crp_test_timely_den = check_timeliness('C反应蛋白检查日期', 180)
        b2m_test_timely_num, b2m_test_timely_den = check_timeliness('β2微球蛋白检查日期', 180)
        tg_test_timely_num, tg_test_timely_den = check_timeliness('甘油三酯检查日期', 180)
        infection_test_timely_num, infection_test_timely_den = check_timeliness('感染性疾病筛查检查日期', 180)
        
        # 铁蛋白及转铁蛋白时效性
        if '铁蛋白检查日期' in df.columns and '转铁蛋白饱和度检验日期' in df.columns:
            ferritin_transferrin_timely = ((pd.to_datetime(df['铁蛋白检查日期'], errors='coerce') >= (sheet_end_date - timedelta(days=180))) & 
                                         (pd.to_datetime(df['转铁蛋白饱和度检验日期'], errors='coerce') >= (sheet_end_date - timedelta(days=180))))
            ferritin_transferrin_timely_num = ferritin_transferrin_timely.sum()
            ferritin_transferrin_timely_den = total_patients
        else:
            ferritin_transferrin_timely_num, ferritin_transferrin_timely_den = 0, 0
        
        # 多项检查同时合格率
        multi_test_cols = ['血钙检查日期', '白蛋白检查日期', 'β2微球蛋白检查日期', '甘油三酯检查日期']
        if all(col in df.columns for col in multi_test_cols):
            valid = pd.Series(True, index=df.index)
            for col in multi_test_cols:
                test_date = pd.to_datetime(df[col], errors='coerce')
                valid &= (sheet_end_date - test_date).dt.days <= 90
            multi_test_timely_num = valid.sum()
            multi_test_timely_den = len(valid)
        else:
            multi_test_timely_num, multi_test_timely_den = 0, 0
        
        # CKD-MBD相关统计
        phosphorus = df['血磷']
        calcium = df['血钙']
        pth = df['甲状旁腺激素']
        
        ckd_mbd_qualified = ((phosphorus >= 1.13) & (phosphorus <= 1.78) & 
                            (calcium >= 2.1) & (calcium <= 2.5) & 
                            (pth >= 150) & (pth <= 300)).sum()
        
        # 血磷分层
        p_lt_113 = (phosphorus < 1.13).sum()
        p_113_178 = ((phosphorus >= 1.13) & (phosphorus <= 1.78)).sum()
        p_gt_178 = (phosphorus > 1.78).sum()
        
        # 血钙分层
        ca_lt_21 = (calcium < 2.1).sum()
        ca_21_25 = ((calcium >= 2.1) & (calcium <= 2.5)).sum()
        ca_gt_25 = (calcium > 2.5).sum()
        
        # PTH分层
        pth_lt_150 = (pth < 150).sum()
        pth_150_300 = ((pth >= 150) & (pth <= 300)).sum()
        pth_gt_300 = (pth > 300).sum()
        
        # 铁蛋白统计
        ferritin = df['铁蛋白']
        ferritin_mean = ferritin.mean()
        ferritin_max = ferritin.max()
        ferritin_min = ferritin.min()
        ferritin_std = ferritin.std()
        
        # Kt/V和URR统计
        ktv_gt_12 = (df['Kt/V'] > 1.2)
        urr_gt_65 = (df['URR'] > 0.65)
        ktv_urr_qualified = (ktv_gt_12 & urr_gt_65).sum()
        
        # 动静脉内瘘长期生存率统计
        if '通路类型' in df.columns and '启用日期' in df.columns:
            avf_patients = df[df['通路类型'] == '自体动静脉内瘘']
        
            if not avf_patients.empty:
                start_dates = pd.to_datetime(avf_patients['启用日期'], errors='coerce')
                days_diff = (sheet_end_date - start_dates).dt.days
                avf_long_term = (days_diff > 730).sum()
                avf_long_term_den = len(avf_patients)
                avf_survival_rate = avf_long_term / avf_long_term_den * 100 if avf_long_term_den > 0 else 0
            else:
                avf_long_term, avf_long_term_den, avf_survival_rate = 0, 0, 0
        else:
            avf_long_term, avf_long_term_den, avf_survival_rate = 0, 0, 0
        
        # 存储结果
        results['月份'].append(sheet_name)
        
        # 性别比
        results['男性比例'].append(round(male_count / total_patients * 100, 2) if total_patients > 0 else 0)
        results['男性比例_分子'].append(male_count)
        results['男性比例_分母'].append(total_patients)
        results['女性比例'].append(round(female_count / total_patients * 100, 2) if total_patients > 0 else 0)
        results['女性比例_分子'].append(female_count)
        results['女性比例_分母'].append(total_patients)
        
        # 血红蛋白
        results['血红蛋白<110比例'].append(round(hb_lt_110 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血红蛋白<110比例_分子'].append(hb_lt_110)
        results['血红蛋白<110比例_分母'].append(total_patients)
        results['血红蛋白110-130比例'].append(round(hb_110_130 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血红蛋白110-130比例_分子'].append(hb_110_130)
        results['血红蛋白110-130比例_分母'].append(total_patients)
        results['血红蛋白>130比例'].append(round(hb_gt_130 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血红蛋白>130比例_分子'].append(hb_gt_130)
        results['血红蛋白>130比例_分母'].append(total_patients)
        
        # 血钾
        results['血钾<3.5比例'].append(round(k_lt_35 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钾<3.5比例_分子'].append(k_lt_35)
        results['血钾<3.5比例_分母'].append(total_patients)
        results['血钾3.5-5比例'].append(round(k_35_5 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钾3.5-5比例_分子'].append(k_35_5)
        results['血钾3.5-5比例_分母'].append(total_patients)
        results['血钾>5比例'].append(round(k_gt_5 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钾>5比例_分子'].append(k_gt_5)
        results['血钾>5比例_分母'].append(total_patients)
        
        # 白蛋白
        results['白蛋白>35比例'].append(round(albumin_gt_35 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['白蛋白>35比例_分子'].append(albumin_gt_35)
        results['白蛋白>35比例_分母'].append(total_patients)
        
        # 高血压
        results['高血压控制率(<65岁)'].append(round(young_controlled / young_total * 100, 2) if young_total > 0 else 0)
        results['高血压控制率(<65岁)_分子'].append(young_controlled)
        results['高血压控制率(<65岁)_分母'].append(young_total)
        results['高血压控制率(≥65岁)'].append(round(elder_controlled / elder_total * 100, 2) if elder_total > 0 else 0)
        results['高血压控制率(≥65岁)_分子'].append(elder_controlled)
        results['高血压控制率(≥65岁)_分母'].append(elder_total)
        results['高血压总控制率'].append(round(total_controlled / total_patients * 100, 2) if total_patients > 0 else 0)
        results['高血压总控制率_分子'].append(total_controlled)
        results['高血压总控制率_分母'].append(total_patients)
        
        # 体重增长率
        results['体重增长率<5%比例'].append(round(weight_growth_lt_5 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['体重增长率<5%比例_分子'].append(weight_growth_lt_5)
        results['体重增长率<5%比例_分母'].append(total_patients)
        
        # 定时检验性
        results['血常规定时检验合格率'].append(round(blood_test_timely_num / blood_test_timely_den * 100, 2) if blood_test_timely_den > 0 else 0)
        results['血常规定时检验合格率_分子'].append(blood_test_timely_num)
        results['血常规定时检验合格率_分母'].append(blood_test_timely_den)
        results['铁蛋白及转铁蛋白时效合格率'].append(round(ferritin_transferrin_timely_num / ferritin_transferrin_timely_den * 100, 2) if ferritin_transferrin_timely_den > 0 else 0)
        results['铁蛋白及转铁蛋白时效合格率_分子'].append(ferritin_transferrin_timely_num)
        results['铁蛋白及转铁蛋白时效合格率_分母'].append(ferritin_transferrin_timely_den)
        results['KtV>1.2且URR>65%比例'].append(round(ktv_urr_qualified / total_patients * 100, 2) if total_patients > 0 else 0)
        results['KtV>1.2且URR>65%比例_分子'].append(ktv_urr_qualified)
        results['KtV>1.2且URR>65%比例_分母'].append(total_patients)
        results['透析充分性定时检验合格率'].append(round(dialysis_test_timely_num / dialysis_test_timely_den * 100, 2) if dialysis_test_timely_den > 0 else 0)
        results['透析充分性定时检验合格率_分子'].append(dialysis_test_timely_num)
        results['透析充分性定时检验合格率_分母'].append(dialysis_test_timely_den)
        
        # CKD-MBD
        results['CKD-MBD达标率'].append(round(ckd_mbd_qualified / total_patients * 100, 2) if total_patients > 0 else 0)
        results['CKD-MBD达标率_分子'].append(ckd_mbd_qualified)
        results['CKD-MBD达标率_分母'].append(total_patients)
        results['CKD-MBD未达标率'].append(round((total_patients - ckd_mbd_qualified) / total_patients * 100, 2) if total_patients > 0 else 0)
        results['CKD-MBD未达标率_分子'].append(total_patients - ckd_mbd_qualified)
        results['CKD-MBD未达标率_分母'].append(total_patients)
        
        # 血磷
        results['血磷<1.13比例'].append(round(p_lt_113 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血磷<1.13比例_分子'].append(p_lt_113)
        results['血磷<1.13比例_分母'].append(total_patients)
        results['血磷1.13-1.78比例'].append(round(p_113_178 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血磷1.13-1.78比例_分子'].append(p_113_178)
        results['血磷1.13-1.78比例_分母'].append(total_patients)
        results['血磷>1.78比例'].append(round(p_gt_178 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血磷>1.78比例_分子'].append(p_gt_178)
        results['血磷>1.78比例_分母'].append(total_patients)
        
        # 血钙
        results['血钙<2.1比例'].append(round(ca_lt_21 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钙<2.1比例_分子'].append(ca_lt_21)
        results['血钙<2.1比例_分母'].append(total_patients)
        results['血钙2.1-2.5比例'].append(round(ca_21_25 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钙2.1-2.5比例_分子'].append(ca_21_25)
        results['血钙2.1-2.5比例_分母'].append(total_patients)
        results['血钙>2.5比例'].append(round(ca_gt_25 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['血钙>2.5比例_分子'].append(ca_gt_25)
        results['血钙>2.5比例_分母'].append(total_patients)
        
        # PTH
        results['PTH<150比例'].append(round(pth_lt_150 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['PTH<150比例_分子'].append(pth_lt_150)
        results['PTH<150比例_分母'].append(total_patients)
        results['PTH150-300比例'].append(round(pth_150_300 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['PTH150-300比例_分子'].append(pth_150_300)
        results['PTH150-300比例_分母'].append(total_patients)
        results['PTH>300比例'].append(round(pth_gt_300 / total_patients * 100, 2) if total_patients > 0 else 0)
        results['PTH>300比例_分子'].append(pth_gt_300)
        results['PTH>300比例_分母'].append(total_patients)
        
        # 其他定时检验性
        results['iPTH定时检验合格率'].append(round(pth_test_timely_num / pth_test_timely_den * 100, 2) if pth_test_timely_den > 0 else 0)
        results['iPTH定时检验合格率_分子'].append(pth_test_timely_num)
        results['iPTH定时检验合格率_分母'].append(pth_test_timely_den)  
        results['前白蛋白定时检验合格率'].append(round(prealbumin_test_timely_num / prealbumin_test_timely_den * 100, 2) if prealbumin_test_timely_den > 0 else 0)
        results['前白蛋白定时检验合格率_分子'].append(prealbumin_test_timely_num)
        results['前白蛋白定时检验合格率_分母'].append(prealbumin_test_timely_den)
        results['CRP定时检验合格率'].append(round(crp_test_timely_num / crp_test_timely_den * 100, 2) if crp_test_timely_den > 0 else 0)
        results['CRP定时检验合格率_分子'].append(crp_test_timely_num)
        results['CRP定时检验合格率_分母'].append(crp_test_timely_den)
        results['β2微球蛋白定时检验合格率'].append(round(b2m_test_timely_num / b2m_test_timely_den * 100, 2) if b2m_test_timely_den > 0 else 0)
        results['β2微球蛋白定时检验合格率_分子'].append(b2m_test_timely_num)
        results['β2微球蛋白定时检验合格率_分母'].append(b2m_test_timely_den)
        results['血液生化定时检验完成率'].append(round(multi_test_timely_num / multi_test_timely_den * 100, 2) if multi_test_timely_den > 0 else 0)
        results['血液生化定时检验完成率_分子'].append(multi_test_timely_num)
        results['血液生化定时检验完成率_分母'].append(multi_test_timely_den)
        results['感染筛查时效合格率'].append(round(infection_test_timely_num / infection_test_timely_den * 100, 2) if infection_test_timely_den > 0 else 0)
        results['感染筛查时效合格率_分子'].append(infection_test_timely_num)
        results['感染筛查时效合格率_分母'].append(infection_test_timely_den)
        
        # 铁蛋白统计
        results['铁蛋白平均值'].append(round(ferritin_mean, 2) if not pd.isna(ferritin_mean) else 0)
        results['铁蛋白最大值'].append(round(ferritin_max, 2) if not pd.isna(ferritin_max) else 0)
        results['铁蛋白最小值'].append(round(ferritin_min, 2) if not pd.isna(ferritin_min) else 0)
        results['铁蛋白标准差'].append(round(ferritin_std, 2) if not pd.isna(ferritin_std) else 0)
        
        # 动静脉内瘘长期生存率
        results['动静脉内瘘长期生存率'].append(round(avf_survival_rate, 2) if not pd.isna(avf_survival_rate) else 0)
        results['动静脉内瘘长期生存率_分子'].append(avf_long_term)
        results['动静脉内瘘长期生存率_分母'].append(avf_long_term_den)

    return pd.DataFrame(results)

def generate_charts(result_df, output_dir):
    """生成所有图表"""
    months = result_df['月份']
    x = np.arange(len(months))
    
    # 设置中文字体
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'PingFang HK', 'Microsoft YaHei']
    plt.rcParams['axes.unicode_minus'] = False
    
    # 1. 总人数趋势图
    plt.figure(figsize=(12, 6), dpi=300)
    plt.plot(months, result_df['总人数'], marker='o', color='#1f77b4', linewidth=2)
    
    for i, txt in enumerate(result_df['总人数']):
        plt.text(i, txt + 0.5, str(txt), ha='center')
    
    plt.xlabel('月份', fontsize=12)
    plt.ylabel('患者人数', fontsize=12)
    plt.title('每月患者总人数趋势', fontsize=14)
    plt.xticks(rotation=45)
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '0_每月患者总人数趋势.png'), bbox_inches='tight')
    plt.close()
    
    # 2. 性别比统计（柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    width = 0.35

    plt.bar(x - width/2, result_df['男性比例'], width, label='男性', color='#1f77b4')
    plt.bar(x + width/2, result_df['女性比例'], width, label='女性', color='#ff7f0e')

    for i in x:
        plt.text(i - width/2, result_df['男性比例'].iloc[i] + 1, f"{result_df['男性比例'].iloc[i]:.2f}%", ha='center')
        plt.text(i + width/2, result_df['女性比例'].iloc[i] + 1, f"{result_df['女性比例'].iloc[i]:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('比例(%)', fontsize=12)
    plt.title('性别比例统计', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '1_性别比例统计.png'), bbox_inches='tight')
    plt.close()

    # 3. 血红蛋白分层统计（百分比堆叠柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    bottom = np.zeros(len(months))
    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1']
    labels = ['<110', '110-130', '>130']

    for i, col in enumerate(['血红蛋白<110比例', '血红蛋白110-130比例', '血红蛋白>130比例']):
        plt.bar(x, result_df[col], bottom=bottom, label=labels[i], color=colors[i])
        bottom += result_df[col]

    for j in range(len(months)):
        current_bottom = 0
        for i, col in enumerate(['血红蛋白<110比例', '血红蛋白110-130比例', '血红蛋白>130比例']):
            value = result_df[col].iloc[j]
            if value > 0:
                plt.text(x[j], current_bottom + value/2, f'{value:.2f}%', 
                        ha='center', va='center', color='white', fontsize=8)
            current_bottom += value

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('血红蛋白分层统计(g/L)', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend(title='血红蛋白', bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '2_血红蛋白分层统计.png'), bbox_inches='tight')
    plt.close()

    # 4. 铁蛋白统计指标（折线图+误差范围）
    plt.figure(figsize=(12, 6), dpi=300)
    plt.plot(months, result_df['铁蛋白平均值'], label='平均值', marker='o', color='#8c564b')
    plt.fill_between(months, 
                    result_df['铁蛋白平均值']-result_df['铁蛋白标准差'], 
                    result_df['铁蛋白平均值']+result_df['铁蛋白标准差'],
                    alpha=0.2, label='标准差范围', color='#8c564b')
    plt.scatter(months, result_df['铁蛋白最大值'], label='最大值', marker='^', color='#e377c2')
    plt.scatter(months, result_df['铁蛋白最小值'], label='最小值', marker='v', color='#7f7f7f')

    for i, txt in enumerate(result_df['铁蛋白平均值']):
        plt.annotate(f"{txt:.1f}", (months[i], result_df['铁蛋白平均值'][i]), 
                    textcoords="offset points", xytext=(0,10), ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('铁蛋白水平(μg/L)', fontsize=12)
    plt.title('铁蛋白统计指标', fontsize=14)
    plt.xticks(rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '3_铁蛋白统计指标.png'), bbox_inches='tight')
    plt.close()

    # 5. 血钾分层统计（百分比堆叠柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    bottom = np.zeros(len(months))
    colors = ['#FF9999', '#66B2FF', '#99FF99']
    labels = ['<3.5', '3.5-5', '>5']

    for i, col in enumerate(['血钾<3.5比例', '血钾3.5-5比例', '血钾>5比例']):
        plt.bar(x, result_df[col], bottom=bottom, label=labels[i], color=colors[i])
        bottom += result_df[col]

    for j in range(len(months)):
        current_bottom = 0
        for i, col in enumerate(['血钾<3.5比例', '血钾3.5-5比例', '血钾>5比例']):
            value = result_df[col].iloc[j]
            if value > 0:
                plt.text(x[j], current_bottom + value/2, f'{value:.2f}%', 
                        ha='center', va='center', color='black', fontsize=8)
            current_bottom += value

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('血钾分层统计(mmol/L)', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend(title='血钾范围', bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '4_血钾分层统计.png'), bbox_inches='tight')
    plt.close()

    # 6. 白蛋白统计（柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    plt.bar(months, result_df['白蛋白>35比例'], color='#9467bd')

    for i, txt in enumerate(result_df['白蛋白>35比例']):
        plt.text(i, txt+1, f"{txt:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('比例(%)', fontsize=12)
    plt.title('血清白蛋白>35g/L患者比例', fontsize=14)
    plt.xticks(rotation=45)
    plt.ylim(0, 110)
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '5_白蛋白统计.png'), bbox_inches='tight')
    plt.close()

    # 7. 高血压控制率（分组柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    width = 0.25

    plt.bar(x - width, result_df['高血压控制率(<65岁)'], width, label='<65岁', color='#1f77b4')
    plt.bar(x, result_df['高血压控制率(≥65岁)'], width, label='≥65岁', color='#ff7f0e')
    plt.bar(x + width, result_df['高血压总控制率'], width, label='总体', color='#2ca02c')

    for i in x:
        plt.text(i - width, result_df['高血压控制率(<65岁)'].iloc[i] + 1, f"{result_df['高血压控制率(<65岁)'].iloc[i]:.2f}%", ha='center')
        plt.text(i, result_df['高血压控制率(≥65岁)'].iloc[i] + 1, f"{result_df['高血压控制率(≥65岁)'].iloc[i]:.2f}%", ha='center')
        plt.text(i + width, result_df['高血压总控制率'].iloc[i] + 1, f"{result_df['高血压总控制率'].iloc[i]:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('控制率(%)', fontsize=12)
    plt.title('高血压控制率统计', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '6_高血压控制率统计.png'), bbox_inches='tight')
    plt.close()

    # 8. 体重增长率统计（柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    plt.bar(months, result_df['体重增长率<5%比例'], color='#17becf')

    for i, txt in enumerate(result_df['体重增长率<5%比例']):
        plt.text(i, txt+1, f"{txt:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('比例(%)', fontsize=12)
    plt.title('透析间期体重增长<5%患者比例', fontsize=14)
    plt.xticks(rotation=45)
    plt.ylim(0, 110)
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '7_体重增长率统计.png'), bbox_inches='tight')
    plt.close()

    # 9. 血常规和铁蛋白定时检验性统计（双指标柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    width = 0.35

    plt.bar(x - width/2, result_df['血常规定时检验合格率'], width, label='血常规检查(≤90天)', color='#1f77b4')
    plt.bar(x + width/2, result_df['铁蛋白及转铁蛋白时效合格率'], width, label='铁蛋白及转铁蛋白检查(≤180天)', color='#ff7f0e')

    for i in x:
        plt.text(i - width/2, result_df['血常规定时检验合格率'].iloc[i] + 1, f"{result_df['血常规定时检验合格率'].iloc[i]:.2f}%", ha='center')
        plt.text(i + width/2, result_df['铁蛋白及转铁蛋白时效合格率'].iloc[i] + 1, f"{result_df['铁蛋白及转铁蛋白时效合格率'].iloc[i]:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('合格率(%)', fontsize=12)
    plt.title('血常规和铁蛋白定时检验性统计', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '8_血常规和铁蛋白定时检验性统计.png'), bbox_inches='tight')
    plt.close()

    # 10. 透析充分性统计（双指标柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    width = 0.35

    plt.bar(x - width/2, result_df['KtV>1.2且URR>65%比例'], width, label='Kt/V>1.2且URR>65%', color='#9467bd')
    plt.bar(x + width/2, result_df['透析充分性定时检验合格率'], width, label='透析充分性定时检验(≤180天)', color='#8c564b')

    for i in x:
        plt.text(i - width/2, result_df['KtV>1.2且URR>65%比例'].iloc[i] + 1, f"{result_df['KtV>1.2且URR>65%比例'].iloc[i]:.2f}%", ha='center')
        plt.text(i + width/2, result_df['透析充分性定时检验合格率'].iloc[i] + 1, f"{result_df['透析充分性定时检验合格率'].iloc[i]:.2f}%", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('比例/合格率(%)', fontsize=12)
    plt.title('透析充分性统计', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '9_透析充分性统计.png'), bbox_inches='tight')
    plt.close()

    # 11.1 CKD-MBD达标率（柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    width = 0.6

    p1 = plt.bar(x, result_df['CKD-MBD达标率'], width, label='达标', color='#2ca02c')
    p2 = plt.bar(x, result_df['CKD-MBD未达标率'], width, bottom=result_df['CKD-MBD达标率'], 
                label='未达标', color='#d62728')

    for i in range(len(months)):
        if result_df['CKD-MBD达标率'].iloc[i] > 5:
            plt.text(x[i], result_df['CKD-MBD达标率'].iloc[i]/2, 
                    f"{result_df['CKD-MBD达标率'].iloc[i]:.1f}%", 
                    ha='center', va='center', color='white', fontweight='bold')
        
        if result_df['CKD-MBD未达标率'].iloc[i] > 5:
            plt.text(x[i], result_df['CKD-MBD达标率'].iloc[i] + result_df['CKD-MBD未达标率'].iloc[i]/2, 
                    f"{result_df['CKD-MBD未达标率'].iloc[i]:.1f}%", 
                    ha='center', va='center', color='white', fontweight='bold')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('CKD-MBD达标率', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend(loc='upper right')
    plt.ylim(0, 110)
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '10.1_CKD-MBD达标率.png'), bbox_inches='tight')
    plt.close()

    # 11.2 血磷分层统计（百分比堆叠柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    bottom = np.zeros(len(months))
    colors = ['#FF9999', '#66B2FF', '#99FF99']
    labels = ['<1.13', '1.13-1.78', '>1.78']

    for i, col in enumerate(['血磷<1.13比例', '血磷1.13-1.78比例', '血磷>1.78比例']):
        plt.bar(months, result_df[col], bottom=bottom, label=labels[i], color=colors[i])
        bottom += result_df[col]

    for j in range(len(months)):
        current_bottom = 0
        for i, col in enumerate(['血磷<1.13比例', '血磷1.13-1.78比例', '血磷>1.78比例']):
            value = result_df[col].iloc[j]
            if value > 0:
                plt.text(j, current_bottom + value/2, f'{value:.2f}%', 
                        ha='center', va='center', color='black', fontsize=8)
            current_bottom += value

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('血磷分层统计(mmol/L)', fontsize=14)
    plt.xticks(rotation=45)
    plt.legend(title='血磷范围', bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '10.2_血磷分层统计.png'), bbox_inches='tight')
    plt.close()

    # 11.3 血钙分层统计（百分比堆叠柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    bottom = np.zeros(len(months))
    colors = ['#FF9999', '#66B2FF', '#99FF99']
    labels = ['<2.1', '2.1-2.5', '>2.5']

    for i, col in enumerate(['血钙<2.1比例', '血钙2.1-2.5比例', '血钙>2.5比例']):
        plt.bar(months, result_df[col], bottom=bottom, label=labels[i], color=colors[i])
        bottom += result_df[col]

    for j in range(len(months)):
        current_bottom = 0
        for i, col in enumerate(['血钙<2.1比例', '血钙2.1-2.5比例', '血钙>2.5比例']):
            value = result_df[col].iloc[j]
            if value > 0:
                plt.text(j, current_bottom + value/2, f'{value:.2f}%', 
                        ha='center', va='center', color='black', fontsize=8)
            current_bottom += value

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('血钙分层统计(mmol/L)', fontsize=14)
    plt.xticks(rotation=45)
    plt.legend(title='血钙范围', bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '10.3_血钙分层统计.png'), bbox_inches='tight')
    plt.close()

    # 11.4 PTH分层统计（百分比堆叠柱状图）
    plt.figure(figsize=(12, 6), dpi=300)
    bottom = np.zeros(len(months))
    colors = ['#FF9999', '#66B2FF', '#99FF99']
    labels = ['<150', '150-300', '>300']

    for i, col in enumerate(['PTH<150比例', 'PTH150-300比例', 'PTH>300比例']):
        plt.bar(months, result_df[col], bottom=bottom, label=labels[i], color=colors[i])
        bottom += result_df[col]

    for j in range(len(months)):
        current_bottom = 0
        for i, col in enumerate(['PTH<150比例', 'PTH150-300比例', 'PTH>300比例']):
            value = result_df[col].iloc[j]
            if value > 0:
                plt.text(j, current_bottom + value/2, f'{value:.2f}%', 
                        ha='center', va='center', color='black', fontsize=8)
            current_bottom += value

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('百分比(%)', fontsize=12)
    plt.title('甲状旁腺激素分层统计(pg/mL)', fontsize=14)
    plt.xticks(rotation=45)
    plt.legend(title='PTH范围', bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '10.4_PTH分层统计.png'), bbox_inches='tight')
    plt.close()

    # 12. 其他定时检验性统计（多指标柱状图）
    plt.figure(figsize=(16, 8), dpi=300)
    metrics = ['iPTH定时检验合格率', '前白蛋白定时检验合格率', 'CRP定时检验合格率', 
              'β2微球蛋白定时检验合格率', '血液生化定时检验完成率', '感染筛查时效合格率']
    colors = plt.cm.tab20.colors
    width = 0.12
    
    for i, metric in enumerate(metrics):
        bars = plt.bar(x + (i-2.5)*width, result_df[metric], width, 
               label=metric.replace('定时检验合格率','').replace('时效合格率',''), 
               color=colors[i])
        
        # 在每个柱子上方添加百分比标签
        for j, bar in enumerate(bars):
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width()/2., height + 1,
                    f"{result_df[metric].iloc[j]:.2f}%",
                    ha='center', va='bottom', fontsize=8)
    
    plt.xlabel('月份', fontsize=12)
    plt.ylabel('合格率(%)', fontsize=12)
    plt.title('其他定时检验性统计', fontsize=14)
    plt.xticks(x, months, rotation=45)
    plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '11_其他定时检验性统计.png'), bbox_inches='tight')
    plt.close()

    # 13. 动静脉内瘘长期生存率图表
    plt.figure(figsize=(12, 6), dpi=300)
    plt.bar(months, result_df['动静脉内瘘长期生存率'], color='#e377c2')

    for i, (rate, num, den) in enumerate(zip(
        result_df['动静脉内瘘长期生存率'],
        result_df['动静脉内瘘长期生存率_分子'],
        result_df['动静脉内瘘长期生存率_分母']
    )):
        plt.text(i, rate + 1, f"{rate:.1f}% ({num}/{den})", ha='center')

    plt.xlabel('月份', fontsize=12)
    plt.ylabel('生存率(%)', fontsize=12)
    plt.title('自体动静脉内瘘长期生存率(>2年)', fontsize=14)
    plt.xticks(rotation=45)
    plt.ylim(0, 110)
    plt.tight_layout()
    plt.savefig(os.path.join(output_dir, '12_动静脉内瘘长期生存率.png'), bbox_inches='tight')
    plt.close()

def create_ppt_from_images(output_dir, log_callback=None):
    """将所有图片创建为PPT文档，一个图片一页"""
    try:
        if log_callback:
            log_callback("开始生成PPT文档...")
        
        # 创建PPT对象
        prs = Presentation()
        
        # 获取所有图片文件
        image_files = []
        for file in os.listdir(output_dir):
            if file.lower().endswith('.png') and not file.startswith('.'):
                image_files.append(file)
        
        image_files.sort()  # 按名称排序
        
        # 为每个图片创建一页
        for i, image_file in enumerate(image_files):
            if log_callback:
                log_callback(f"添加图片到PPT: {image_file} ({i+1}/{len(image_files)})")
            
            # 添加空白幻灯片
            slide_layout = prs.slide_layouts[6]  # 空白布局
            slide = prs.slides.add_slide(slide_layout)
            
            # 添加标题
            title_text = image_file.split('_', 1)[1].replace('.png', '') if '_' in image_file else image_file.replace('.png', '')
            
            # 创建标题文本框
            title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.2), Inches(9), Inches(0.8))
            title_frame = title_box.text_frame
            title_frame.text = title_text
            title_paragraph = title_frame.paragraphs[0]
            title_paragraph.font.size = 440000  # 以 EMU 为单位，约等于 24pt
            title_paragraph.font.bold = True
            
            # 添加图片
            image_path = os.path.join(output_dir, image_file)
            
            # 计算图片位置和大小
            left = Inches(0.5)
            top = Inches(1.2)
            max_width = Inches(9)
            max_height = Inches(6)
            
            # 添加图片到幻灯片
            slide.shapes.add_picture(image_path, left, top, width=max_width, height=max_height)
        
        # 保存PPT
        ppt_path = os.path.join(output_dir, '透析质控指标分析报告.pptx')
        prs.save(ppt_path)
        
        if log_callback:
            log_callback(f"PPT文档已保存: {ppt_path}")
        
        return ppt_path
        
    except Exception as e:
        if log_callback:
            log_callback(f"生成PPT时出错: {str(e)}")
        raise e

def create_word_from_images(output_dir, log_callback=None):
    """将所有图片创建为Word文档，一个图片一页"""
    try:
        if log_callback:
            log_callback("开始生成Word文档...")
        
        # 创建Word文档对象
        doc = Document()
        
        # 设置文档标题
        title = doc.add_heading('透析患者质控指标统计分析报告', 0)
        title.alignment = 1  # 居中对齐
        
        # 获取所有图片文件
        image_files = []
        for file in os.listdir(output_dir):
            if file.lower().endswith('.png') and not file.startswith('.'):
                image_files.append(file)
        
        image_files.sort()  # 按名称排序
        
        # 为每个图片创建一页
        for i, image_file in enumerate(image_files):
            if log_callback:
                log_callback(f"添加图片到Word: {image_file} ({i+1}/{len(image_files)})")
            
            # 添加图片标题
            title_text = image_file.split('_', 1)[1].replace('.png', '') if '_' in image_file else image_file.replace('.png', '')
            heading = doc.add_heading(title_text, level=1)
            heading.alignment = 1  # 居中对齐
            
            # 添加图片
            image_path = os.path.join(output_dir, image_file)
            
            # 添加图片段落
            paragraph = doc.add_paragraph()
            paragraph.alignment = 1  # 居中对齐
            run = paragraph.runs[0] if paragraph.runs else paragraph.add_run()
            
            # 插入图片，设置适当大小
            try:
                run.add_picture(image_path, width=DocxInches(6))
            except:
                # 如果图片太大，尝试设置更小的尺寸
                run.add_picture(image_path, width=DocxInches(5))
            
            # 如果不是最后一个图片，添加分页符
            if i < len(image_files) - 1:
                doc.add_page_break()
        
        # 保存Word文档
        word_path = os.path.join(output_dir, '透析质控指标分析报告.docx')
        doc.save(word_path)
        
        if log_callback:
            log_callback(f"Word文档已保存: {word_path}")
        
        return word_path
        
    except Exception as e:
        if log_callback:
            log_callback(f"生成Word文档时出错: {str(e)}")
        raise e

# ==================== Tkinter界面部分 ====================
class DialysisQualityApp:
    def __init__(self, root):
        self.root = root
        self.root.title("透析患者质控指标统计分析系统")
        self.root.geometry("1200x800")
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        
        self.input_file = ""
        self.output_dir = ""
        self.result_df = None
        self.current_image = None
        self.chart_files = []
        self.is_analyzing = False
        self.log_text = None
        self.progress_var = None
        self.analyze_button = None
        
        self.create_widgets()
    
    def on_close(self):
        """处理窗口关闭事件"""
        if hasattr(self, 'current_image'):
            del self.current_image
        self.root.destroy()
        os._exit(0)
    
    def create_widgets(self):
        # 顶部控制面板
        control_frame = ttk.Frame(self.root, padding="10")
        control_frame.pack(fill=tk.X)
        
        # 输入文件选择
        ttk.Label(control_frame, text="输入文件:").grid(row=0, column=0, sticky=tk.W)
        self.input_entry = ttk.Entry(control_frame, width=50)
        self.input_entry.grid(row=0, column=1, padx=5)
        ttk.Button(control_frame, text="浏览...", command=self.browse_input).grid(row=0, column=2)
        
        # 输出目录选择
        ttk.Label(control_frame, text="输出目录:").grid(row=1, column=0, sticky=tk.W)
        self.output_entry = ttk.Entry(control_frame, width=50)
        self.output_entry.grid(row=1, column=1, padx=5)
        ttk.Button(control_frame, text="浏览...", command=self.browse_output).grid(row=1, column=2)
        
        # 按钮区域
        button_frame = ttk.Frame(control_frame)
        button_frame.grid(row=2, column=0, columnspan=3, pady=10)
        
        self.analyze_button = ttk.Button(button_frame, text="开始分析", command=self.analyze_data)
        self.analyze_button.pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="下载数据模板", command=self.download_template).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="保存统计结果", command=self.save_results).pack(side=tk.LEFT, padx=5)
        
        # 进度条
        progress_frame = ttk.Frame(control_frame)
        progress_frame.grid(row=3, column=0, columnspan=3, pady=5, sticky=tk.W+tk.E)
        
        ttk.Label(progress_frame, text="进度:").pack(side=tk.LEFT)
        self.progress_var = tk.StringVar(value="准备就绪")
        self.progress_label = ttk.Label(progress_frame, textvariable=self.progress_var)
        self.progress_label.pack(side=tk.LEFT, padx=10)
        
        self.progress_bar = ttk.Progressbar(progress_frame, length=300, mode='indeterminate')
        self.progress_bar.pack(side=tk.LEFT, padx=10)
        
        # 主内容区域
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True)
        
        # 创建标签页
        self.results_tab = ttk.Frame(self.notebook)
        self.charts_tab = ttk.Frame(self.notebook)
        self.errors_tab = ttk.Frame(self.notebook)
        self.log_tab = ttk.Frame(self.notebook)
        self.about_tab = ttk.Frame(self.notebook)
        
        self.notebook.add(self.results_tab, text="统计结果")
        self.notebook.add(self.charts_tab, text="分析图表")
        self.notebook.add(self.errors_tab, text="错误记录")
        self.notebook.add(self.log_tab, text="处理日志")
        self.notebook.add(self.about_tab, text="关于")
        
        # 初始化标签页内容
        self.init_results_tab()
        self.init_charts_tab()
        self.init_errors_tab()
        self.init_log_tab()
        self.init_about_tab()
    
    def save_results(self):
        """保存统计结果到Excel文件"""
        if self.result_df is None:
            messagebox.showwarning("警告", "请先进行分析再保存结果")
            return
        
        try:
            file_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                filetypes=[("Excel文件", "*.xlsx")],
                title="保存统计结果"
            )
            
            if file_path:
                # 创建结果数据副本
                result_df = self.result_df.copy()
                
                # 识别百分比列（包含"比例"或"率"但不包含"分子"或"分母"的列）
                percentage_cols = [
                    col for col in result_df.columns 
                    if ('比例' in col or '率' in col) 
                    and '分子' not in col 
                    and '分母' not in col
                ]
                
                # 识别数值型列（不处理百分比列和日期列）
                numeric_cols = [
                    col for col in result_df.columns 
                    if col not in percentage_cols 
                    and result_df[col].dtype in ['int64', 'float64']
                    and not any(x in col for x in ['日期', '分子', '分母'])
                ]
                
                # 处理百分比列 - 添加百分号并保留2位小数
                for col in percentage_cols:
                    result_df[col] = result_df[col].apply(
                        lambda x: f"{float(x):.2f}%" if pd.notna(x) and str(x).replace('.', '').isdigit() else x
                    )
                
                # 处理数值型列 - 保留2位小数但不加百分号
                for col in numeric_cols:
                    result_df[col] = result_df[col].apply(
                        lambda x: f"{float(x):.2f}" if pd.notna(x) and str(x).replace('.', '').isdigit() else x
                    )
                
                # 保存到Excel
                result_df.to_excel(file_path, index=False)
                messagebox.showinfo("成功", f"统计结果已保存到:\n{file_path}")
        
        except Exception as e:
            messagebox.showerror("错误", f"保存结果时出错:\n{str(e)}")
    
    def download_template(self):
        """下载数据模板"""
        try:
            example_count = 5
            template_data = {
                "姓名": [f"患者{i+1}" for i in range(example_count)],
                "性别": ["男", "女", "男", "女", "男"],
                "年龄": [45, 62, 58, 71, 53],
                "血红蛋白": [105, 125, 98, 115, 130],
                "血钾": [3.8, 4.2, 5.1, 3.5, 4.9],
                "白蛋白": [36, 42, 38, 35, 40],
                "平均体重增长率": [4.2, 3.8, 5.1, 4.5, 3.9],
                "Kt/V": [1.3, 1.5, 1.1, 1.4, 1.2],
                "URR": [68, 72, 65, 70, 67],
                "血磷": [1.5, 1.2, 1.8, 1.3, 1.6],
                "血钙": [2.3, 2.1, 2.4, 2.2, 2.5],
                "甲状旁腺激素": [280, 190, 320, 210, 250],
                "铁蛋白": [350, 420, 380, 290, 410],
                "平均收缩压": [115, 97, 178, 145, 100],
                "平均舒张压": [67, 45, 101, 99, 60],
                "血常规检查日期": ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15", "2023-03-01"],
                "铁蛋白检查日期": ["2023-01-10", "2023-01-20", "2023-02-10", "2023-02-20", "2023-03-10"],
                "转铁蛋白饱和度检验日期": ["2023-01-05", "2023-01-25", "2023-02-05", "2023-02-25", "2023-03-05"],
                "透析充分性检查日期": ["2023-01-08", "2023-01-18", "2023-02-08", "2023-02-18", "2023-03-08"],
                "甲状旁腺激素检查日期": ["2023-01-12", "2023-01-22", "2023-02-12", "2023-02-22", "2023-03-12"],
                "前白蛋白检查日期": ["2023-01-07", "2023-01-17", "2023-02-07", "2023-02-17", "2023-03-07"],
                "C反应蛋白检查日期": ["2023-01-09", "2023-01-19", "2023-02-09", "2023-02-19", "2023-03-09"],
                "β2微球蛋白检查日期": ["2023-01-11", "2023-01-21", "2023-02-11", "2023-02-21", "2023-03-11"],
                "启用日期": ["2021-05-15", "2020-11-20", "2019-08-10", "2022-03-05", "2021-09-18"],
                "通路类型": ["自体动静脉内瘘", "人工血管", "中心静脉导管", "自体动静脉内瘘", "人工血管"]
            }
            
            template_df = pd.DataFrame(template_data)
            file_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                filetypes=[("Excel文件", "*.xlsx")],
                title="保存数据模板"
            )
            
            if file_path:
                template_df.to_excel(file_path, index=False)
                messagebox.showinfo("成功", f"模板已保存到:\n{file_path}")
        
        except Exception as e:
            messagebox.showerror("错误", f"生成模板时出错:\n{str(e)}")

    def init_results_tab(self):
        # 结果表格框架
        self.results_frame = ttk.Frame(self.results_tab)
        self.results_frame.pack(fill=tk.BOTH, expand=True)
        
        # 水平滚动条
        self.h_scroll = ttk.Scrollbar(self.results_frame, orient=tk.HORIZONTAL)
        self.h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 垂直滚动条
        self.v_scroll = ttk.Scrollbar(self.results_frame)
        self.v_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 创建Treeview表格
        self.results_table = ttk.Treeview(
            self.results_frame,
            xscrollcommand=self.h_scroll.set,
            yscrollcommand=self.v_scroll.set
        )
        self.results_table.pack(fill=tk.BOTH, expand=True)
        
        # 配置滚动条
        self.h_scroll.config(command=self.results_table.xview)
        self.v_scroll.config(command=self.results_table.yview)
        
        # 绑定事件，使列宽可调整
        self.results_table.bind("<ButtonRelease-1>", self.on_table_click)
    
    def on_table_click(self, event):
        """处理表格点击事件，调整列宽"""
        region = self.results_table.identify("region", event.x, event.y)
        if region == "separator":
            for col in self.results_table["columns"]:
                self.results_table.column(col, width=tk.font.Font().measure(col) + 20)
    
    def init_charts_tab(self):
        # 图表选择和控制面板
        control_frame = ttk.Frame(self.charts_tab)
        control_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(control_frame, text="选择图表:").pack(side=tk.LEFT)
        
        self.chart_selector = ttk.Combobox(control_frame, state="readonly")
        self.chart_selector.pack(side=tk.LEFT, padx=5)
        self.chart_selector.bind("<<ComboboxSelected>>", self.show_selected_chart)
        
        # 图表显示区域
        self.chart_frame = ttk.Frame(self.charts_tab)
        self.chart_frame.pack(fill=tk.BOTH, expand=True)
        
        self.chart_files = []
    
    def init_errors_tab(self):
        # 错误记录表格框架
        self.errors_frame = ttk.Frame(self.errors_tab)
        self.errors_frame.pack(fill=tk.BOTH, expand=True)
        
        h_scroll = ttk.Scrollbar(self.errors_frame, orient=tk.HORIZONTAL)
        h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        
        v_scroll = ttk.Scrollbar(self.errors_frame)
        v_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.errors_table = ttk.Treeview(
            self.errors_frame,
            columns=("sheet", "field", "error"),
            yscrollcommand=v_scroll.set,
            xscrollcommand=h_scroll.set
        )
        self.errors_table.heading("#0", text="ID")
        self.errors_table.heading("sheet", text="工作表")
        self.errors_table.heading("field", text="字段")
        self.errors_table.heading("error", text="错误描述")
        
        self.errors_table.column("#0", width=50, stretch=False)
        self.errors_table.column("sheet", width=150, stretch=False)
        self.errors_table.column("field", width=150, stretch=False)
        self.errors_table.column("error", width=400)
        
        self.errors_table.pack(fill=tk.BOTH, expand=True)
        
        h_scroll.config(command=self.errors_table.xview)
        v_scroll.config(command=self.errors_table.yview)
    
    def init_log_tab(self):
        """初始化日志标签页"""
        # 日志显示区域
        log_frame = ttk.Frame(self.log_tab)
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 日志标题
        ttk.Label(log_frame, text="处理日志:", font=("微软雅黑", 12, "bold")).pack(anchor=tk.W, pady=(0, 5))
        
        # 日志文本区域
        log_text_frame = ttk.Frame(log_frame)
        log_text_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建滚动条
        log_v_scroll = ttk.Scrollbar(log_text_frame)
        log_v_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        log_h_scroll = ttk.Scrollbar(log_text_frame, orient=tk.HORIZONTAL)
        log_h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 创建文本区域
        self.log_text = tk.Text(
            log_text_frame,
            wrap=tk.NONE,
            yscrollcommand=log_v_scroll.set,
            xscrollcommand=log_h_scroll.set,
            font=("Consolas", 10),
            bg="#f8f8f8",
            fg="#333333"
        )
        self.log_text.pack(fill=tk.BOTH, expand=True)
        
        # 配置滚动条
        log_v_scroll.config(command=self.log_text.yview)
        log_h_scroll.config(command=self.log_text.xview)
        
        # 按钮区域
        button_frame = ttk.Frame(log_frame)
        button_frame.pack(fill=tk.X, pady=(5, 0))
        
        ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="保存日志", command=self.save_log).pack(side=tk.LEFT, padx=5)
        
        # 初始化日志
        self.add_log("系统已启动，准备就绪...")
    
    def add_log(self, message):
        """添加日志消息"""
        if self.log_text:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            log_message = f"[{timestamp}] {message}\n"
            
            self.log_text.insert(tk.END, log_message)
            self.log_text.see(tk.END)
            self.root.update()
    
    def clear_log(self):
        """清空日志"""
        if self.log_text:
            self.log_text.delete(1.0, tk.END)
            self.add_log("日志已清空")
    
    def save_log(self):
        """保存日志到文件"""
        if self.log_text:
            try:
                file_path = filedialog.asksaveasfilename(
                    defaultextension=".txt",
                    filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],
                    title="保存日志文件"
                )
                
                if file_path:
                    with open(file_path, 'w', encoding='utf-8') as f:
                        f.write(self.log_text.get(1.0, tk.END))
                    self.add_log(f"日志已保存到: {file_path}")
                    messagebox.showinfo("成功", f"日志已保存到:\n{file_path}")
            except Exception as e:
                self.add_log(f"保存日志失败: {str(e)}")
                messagebox.showerror("错误", f"保存日志时出错:\n{str(e)}")
    
    def browse_input(self):
        file_path = filedialog.askopenfilename(
            title="选择Excel文件",
            filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
        )
        if file_path:
            self.input_entry.delete(0, tk.END)
            self.input_entry.insert(0, file_path)
            self.input_file = file_path
            self.add_log(f"已选择输入文件: {os.path.basename(file_path)}")
    
    def browse_output(self):
        dir_path = filedialog.askdirectory(title="选择输出目录")
        if dir_path:
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, dir_path)
            self.output_dir = dir_path
            os.makedirs(dir_path, exist_ok=True)
            self.add_log(f"已选择输出目录: {dir_path}")
    
    def analyze_data(self):
        if self.is_analyzing:
            messagebox.showwarning("警告", "分析正在进行中，请等待完成")
            return
            
        if not self.input_file:
            messagebox.showerror("错误", "请先选择输入文件")
            return
        
        if not self.output_dir:
            messagebox.showerror("错误", "请先选择输出目录")
            return
        
        # 开始异步分析
        self.start_analysis()
    
    def start_analysis(self):
        """开始异步分析"""
        self.is_analyzing = True
        self.analyze_button.config(state="disabled", text="分析中...")
        self.progress_bar.start()
        self.progress_var.set("正在分析数据...")
        
        # 切换到日志标签页
        self.notebook.select(self.log_tab)
        self.add_log("开始数据分析...")
        
        # 初始化分析步骤
        self.analysis_steps = []
        self.current_step = 0
        
        # 使用after方法分步执行，避免线程问题
        self.root.after(100, self.run_analysis_step)
    
    def run_analysis_step(self):
        """分步执行分析，避免界面卡顿"""
        try:
            if self.current_step == 0:
                # 步骤1: 读取Excel文件
                self.add_log("正在读取Excel文件...")
                self.progress_var.set("读取Excel文件...")
                
                global global_errors
                global_errors = []
                
                self.sheets = pd.read_excel(self.input_file, sheet_name=None)
                self.add_log(f"成功读取Excel文件，共{len(self.sheets)}个工作表")
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 1:
                # 步骤2: 处理数据
                self.add_log("开始处理数据...")
                self.progress_var.set("处理数据中...")
                
                self.result_df = process_data(self.sheets)
                self.add_log("数据处理完成")
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 2:
                # 步骤3: 生成图表
                self.add_log("开始生成图表...")
                self.progress_var.set("生成图表中...")
                
                generate_charts(self.result_df, self.output_dir)
                self.add_log("图表生成完成")
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 3:
                # 步骤4: 生成PPT
                self.add_log("开始生成PPT文档...")
                self.progress_var.set("生成PPT中...")
                
                self.ppt_path = create_ppt_from_images(self.output_dir, self.add_log)
                self.add_log(f"PPT文档生成完成: {os.path.basename(self.ppt_path)}")
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 4:
                # 步骤5: 生成Word文档
                self.add_log("开始生成Word文档...")
                self.progress_var.set("生成Word中...")
                
                self.word_path = create_word_from_images(self.output_dir, self.add_log)
                self.add_log(f"Word文档生成完成: {os.path.basename(self.word_path)}")
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 5:
                # 步骤6: 更新界面
                self.add_log("更新界面显示...")
                self.progress_var.set("更新界面...")
                
                self.update_results_table()
                self.update_charts_list()
                self.update_errors_table()
                
                self.current_step += 1
                self.root.after(500, self.run_analysis_step)
                
            elif self.current_step == 6:
                # 完成
                self.add_log("数据分析完成！")
                self.progress_var.set("分析完成")
                
                # 统计生成的文件
                png_count = len([f for f in os.listdir(self.output_dir) if f.endswith('.png')])
                
                messagebox.showinfo("完成", 
                    f"数据分析完成！\n\n生成文件:\n- 统计图表: {png_count}张\n- PPT报告: {os.path.basename(self.ppt_path)}\n- Word报告: {os.path.basename(self.word_path)}")
                
                self.finish_analysis()
                
        except Exception as e:
            error_msg = f"分析过程中出错: {str(e)}"
            self.add_log(error_msg)
            messagebox.showerror("错误", error_msg)
            self.finish_analysis()
    
    def finish_analysis(self):
        """完成分析，恢复界面状态"""
        self.is_analyzing = False
        self.analyze_button.config(state="normal", text="开始分析")
        self.progress_bar.stop()
        self.progress_var.set("准备就绪")
    
    def update_results_table(self):
        for item in self.results_table.get_children():
            self.results_table.delete(item)
        
        self.results_table["columns"] = []
        
        if self.result_df is not None:
            columns = list(self.result_df.columns)
            self.results_table["columns"] = columns
            
            for col in columns:
                self.results_table.heading(col, text=col, anchor='center')
                col_width = tk.font.Font().measure(col) + 20
                self.results_table.column(col, width=col_width, anchor='center', stretch=False)
            
            for i, row in self.result_df.iterrows():
                values = []
                for col in columns:
                    val = row[col]
                    if isinstance(val, float):
                        if '比例' in col or '率' in col:
                            values.append(f"{val:.2f}%")
                        else:
                            values.append(f"{val:.2f}")
                    else:
                        values.append(str(val))
                self.results_table.insert("", tk.END, text=str(i+1), values=values)
    
    def update_charts_list(self):
        self.chart_files = []
        if os.path.exists(self.output_dir):
            for file in os.listdir(self.output_dir):
                if file.lower().endswith(('.png', '.jpg', '.jpeg')):
                    self.chart_files.append(file)
        
        self.chart_files.sort()
        chart_names = []
        for f in self.chart_files:
            name = f.split('_', 1)[1].replace('.png', '')
            chart_names.append(name)
        
        self.chart_selector["values"] = chart_names
        
        if self.chart_files:
            self.chart_selector.current(0)
            self.show_selected_chart()
    
    def show_selected_chart(self, event=None):
        for widget in self.chart_frame.winfo_children():
            widget.destroy()
        
        index = self.chart_selector.current()
        if index < 0 or index >= len(self.chart_files):
            return
        
        chart_path = os.path.join(self.output_dir, self.chart_files[index])
        
        try:
            img = Image.open(chart_path)
            frame_width = self.chart_frame.winfo_width()
            frame_height = self.chart_frame.winfo_height()
            
            if frame_width < 100 or frame_height < 100:
                frame_width, frame_height = 1000, 700
            
            img_ratio = img.width / img.height
            frame_ratio = frame_width / frame_height
            
            if frame_ratio > img_ratio:
                new_height = frame_height - 20
                new_width = int(new_height * img_ratio)
            else:
                new_width = frame_width - 20
                new_height = int(new_width / img_ratio)
            
            img = img.resize((new_width, new_height), Image.LANCZOS)
            self.current_image = ImageTk.PhotoImage(img)
            img_label = ttk.Label(self.chart_frame, image=self.current_image)
            img_label.pack(fill=tk.BOTH, expand=True)
            
        except Exception as e:
            messagebox.showerror("错误", f"无法显示图表:\n{str(e)}")
    
    def update_errors_table(self):
        for item in self.errors_table.get_children():
            self.errors_table.delete(item)
        
        for i, error in enumerate(global_errors):
            self.errors_table.insert("", tk.END, text=str(i+1), values=error)

    def init_about_tab(self):
        """初始化关于页面"""
        about_frame = ttk.Frame(self.about_tab, padding="20")
        about_frame.pack(fill=tk.BOTH, expand=True)
        
        # 程序标题
        title_label = ttk.Label(
            about_frame, 
            text="透析患者质控指标统计分析系统",
            font=("微软雅黑", 16, "bold")
        )
        title_label.pack(pady=10)
        
        # 版本信息
        version_label = ttk.Label(
            about_frame,
            text="版本: 1.0.0",
            font=("微软雅黑", 12)
        )
        version_label.pack(pady=5)
        
        # 作者信息
        author_label = ttk.Label(
            about_frame,
            text="作者: 郑易军",
            font=("微软雅黑", 12)
        )
        author_label.pack(pady=5)
        
        # 版权信息
        copyright_label = ttk.Label(
            about_frame,
            text="© 2023 版权所有",
            font=("微软雅黑", 10)
        )
        copyright_label.pack(pady=20)
        
        # 添加一个简单的分隔线
        separator = ttk.Separator(about_frame, orient="horizontal")
        separator.pack(fill=tk.X, pady=10)
        
        # 联系信息
        contact_label = ttk.Label(
            about_frame,
            text="联系方式: zhengyijun@example.com",
            font=("微软雅黑", 10)
        )
        contact_label.pack(pady=5)
        
        # 使用说明（可选）
        instruction_label = ttk.Label(
            about_frame,
            text="本软件用于透析患者质控指标的统计分析",
            font=("微软雅黑", 10)
        )
        instruction_label.pack(pady=5)

# 主程序
if __name__ == "__main__":
    root = tk.Tk()
    app = DialysisQualityApp(root)
    
    try:
        root.mainloop()
    except KeyboardInterrupt:
        # 处理Ctrl+C中断
        root.destroy()
        sys.exit()